#include <cstring>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <vector>
#include <asm/unistd.h>
#include <pthread.h>

#include "Process.hpp"

using std::cout;
using std::endl;
using std::vector;

#define LINE_LEN 150

vector <Process> pList;
vector <pthread_t> tid;
vector <int> ids;
unsigned long start;
struct timespec origin;
int factor;

void * runProc(void * arg){
  int id, e;
  struct timespec current;
  id = *(int*)arg;
  printf("%d: %d\n", id, pList[id].getCount());
  while(pList[id].getCount() > 0){
    clock_gettime(CLOCK_REALTIME, &current);
    unsigned long t = difftime(current.tv_sec, origin.tv_sec)*1000000000+(current.tv_nsec - origin.tv_nsec);
    e = pList[id].stepProcess(factor * t);
    if(e < 0){
      printf("Replay: a call failed at time %f from process %d\n", ((double)t)/1000000000.0, id);
    }
  }
  return 0;
}

int main (int argc, char * argv[]){
  int fd, offset;
  int i;
  char line[LINE_LEN], *index;
  char * a[10];
  factor = 1;

  if(argc > 3){
    cout << "Replay: Invalid Command. 'Replay <inputFile> [factor]'\n";
    exit(0);
  }

  if (argc == 1){
    cout << "Replay: An input file must be provided." << endl;
    exit(0);
  }

  if ((fd=open(argv[1], O_RDONLY , 0600)) < 0) {
    cout << "Replay: Cannot open file " << argv[1] << ".\n";
    exit(0);
  }

  if (argc == 3)
    if ((factor = atoi(argv[2])) < 1)
      factor = 1;

  while((offset = read(fd, line, LINE_LEN)) > 0){
    index = strchr(line, '\n')+1;
    *index = '\0';
    lseek(fd, (strlen(line)-offset), SEEK_CUR);
    i = 0;
    a[i++] = strtok(line, " \n");
    while((a[i++] = strtok(NULL, " \n")) != NULL){};
    int j;
    //Get the process or add it to the vector
    if(pList.size() > 0 && atoi(a[0]) == pList[pList.size()-1].getID())
      j = pList.size()-1;
    else{
      pList.push_back(Process(atoi(a[0]), strtoul(a[2], NULL, 10)));
      j = pList.size()-1;
    }
    //Determine and add the System Call
    SysCall * x;
    switch(atoi(a[1])){
      case __NR_read:
        x = new SysRead(strtoul(a[2], NULL, 10), atoi(a[3]), atoi(a[4]), atoi(a[5]));
        pList[j].addCall(x);
        break;
      case __NR_readv:
        x = new SysReadv(strtoul(a[2], NULL, 10), atoi(a[3]));
        pList[j].addCall(x);
        break;
      case __NR_write:
        x = new SysWrite(strtoul(a[2], NULL, 10), atoi(a[3]), atoi(a[4]), atoi(a[5]));
        pList[j].addCall(x);
        break;
      case __NR_writev:
        x = new SysWritev(strtoul(a[2], NULL, 10), atoi(a[3]));
        pList[j].addCall(x);
        break;
      case __NR_open:
        x = new SysOpen(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], atoi(a[5]), atoi(a[6]));
        pList[j].addCall(x);
        break;
      case __NR_close:
        x = new SysClose(strtoul(a[2], NULL, 10), atoi(a[3]), atoi(a[4]));
        pList[j].addCall(x);
        break;
      case __NR_creat:
        x = new SysCreat(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], atoi(a[5]));
        pList[j].addCall(x);
        break;
      case __NR_unlink:
        x = new SysUnlink(strtoul(a[2], NULL, 10), atoi(a[3]), a[4]);
        pList[j].addCall(x);
        break;
      case __NR_chdir:
        x = new SysChdir(strtoul(a[2], NULL, 10), atoi(a[3]));
        pList[j].addCall(x);
        break;
      case __NR_mknod:
        x = new SysMknod(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], atoi(a[5]), atoi(a[6]));
        pList[j].addCall(x);
        break;
      case __NR_mkdir:
        x = new SysMkdir(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], atoi(a[5]));
        pList[j].addCall(x);
        break;
      case __NR_pread64:
        x = new SysPRead(strtoul(a[2], NULL, 10), atoi(a[3]), atoi(a[4]), atoi(a[5]), atoi(a[6]));
        pList[j].addCall(x);
        break;
      case __NR_pwrite64:
        x = new SysPWrite(strtoul(a[2], NULL, 10), atoi(a[3]), atoi(a[4]), atoi(a[5]), atoi(a[6]));
        pList[j].addCall(x);
        break;
      case __NR_rename:
        x = new SysRename(strtoul(a[2], NULL, 10), atoi(a[3]));
        pList[j].addCall(x);
        break;
      case __NR_link:
        x = new SysLink(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], a[5]);
        pList[j].addCall(x);
        break;
      case __NR_symlink:
        x = new SysSymlink(strtoul(a[2], NULL, 10), atoi(a[3]), a[4], a[5]);
        pList[j].addCall(x);
        break;
      case __NR_rmdir:
        x = new SysRmdir(strtoul(a[2], NULL, 10), atoi(a[3]), a[4]);
        pList[j].addCall(x);
        break;
      //ect.
      default:
        break;
    }
  }
  close(fd);

  printf("Replay starting...\n");
  int n = pList.size();
  clock_gettime(CLOCK_REALTIME, &origin);
  start = time(NULL);
  tid.resize(n);
  ids.resize(n);
  for(int i = 0; i < n; i++){
    ids[i] = i;
    pthread_create(&tid[i], NULL, &runProc, &ids[i]);
  }
  for(i = 0; i < n; i++)
    pthread_join(tid[i], NULL);
  printf("Replay: %lu seconds with %d processes.\n", time(NULL) - start, n);
  return 0;
}
